package pers.lipeng.snake;

import javax.security.auth.callback.ConfirmationCallback;
import javax.swing.*;
import javax.swing.Timer;
import java.awt.*;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.util.*;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicBoolean;

/**
 * @author APengG
 */
public class SnakeGame extends JFrame {
    private final static int ROWS = 50;
    private final static int COLS = 50;
    private final static int SNAKE_DEFAULT_LENGTH = 6;
    private final static Map<Integer,String> LEVEL_MAP = new ConcurrentHashMap<>();
    private final static Map<Integer,Integer> LEVEL_TIMEOUT_MAP = new ConcurrentHashMap<>();
    private List<SnakeBody> sankeBodyList = new ArrayList<>();
    private JPanel[][] backPanels = new JPanel[ROWS][COLS];
    private volatile SnakeMoveDirection currentDirection = SnakeMoveDirection.DOWN;
    private AtomicBoolean atomicBoolean = new AtomicBoolean(true);

    private Food food = new Food(0,0);
    private JPanel gamePanel;
    private JPanel scorePane;
    private JLabel score;
    private JLabel levelLabel;
    private JFrame snakeGame;
    private Integer level = 1;
    private int currentScore = 0;
    private Timer timer;

    static {
        LEVEL_MAP.put(1,"简单");
        LEVEL_MAP.put(2,"中等");
        LEVEL_MAP.put(3,"困难");

        LEVEL_TIMEOUT_MAP.put(1,200);
        LEVEL_TIMEOUT_MAP.put(2,100);
        LEVEL_TIMEOUT_MAP.put(3,50);
    }
    public SnakeGame(){
        super("贪吃蛇小游戏");
        snakeGame = this;
        //初始化蛇身
        for(int i = SNAKE_DEFAULT_LENGTH; i>0 ;i--){
            sankeBodyList.add(new SnakeBody(ROWS/2,i));
        }

        this.setSize(900,720);
        Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
        int x = screenSize.width - this.getWidth() >> 1;
        this.setLocation(x,0);
        this.setResizable(false);
        this.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);

        gamePanel = new JPanel();
        GridLayout gridLayout = new GridLayout(ROWS, COLS, 0, 0);
        gamePanel.setLayout(gridLayout);

        for(int i=0;i<ROWS * COLS ;i++){
            JPanel panel = new JPanel();
            panel.setLayout(new BorderLayout());
            panel.setPreferredSize(new Dimension(15,15));
            backPanels[i % ROWS][i / COLS] = panel;
            gamePanel.add(panel);
        }
        initSnake();
        randomProduceFood();

        //计分板
        scorePane = new JPanel();
        scorePane.setBackground(Color.WHITE);
        this.setLayout(new BorderLayout());
        score = new JLabel("当前得分："+currentScore);

        levelLabel = new JLabel("当前难度："+LEVEL_MAP.get(level));
        scorePane.setLayout(new GridLayout(1,3,10,10));
        scorePane.add(levelLabel);
        scorePane.add(score);

        snakeGame.addKeyListener(new SnakeBodyKeyListener());
        this.getContentPane().add(scorePane,BorderLayout.NORTH);
        this.getContentPane().add(gamePanel,BorderLayout.SOUTH);
        this.pack();
        this.setVisible(true);

        loadTimer(LEVEL_TIMEOUT_MAP.get(level));
    }
  public void setLevel(int level){
        this.level = level;
        this.levelLabel.setText("当前难度："+LEVEL_MAP.get(level));
        loadTimer(LEVEL_TIMEOUT_MAP.get(level));
    }

    private void loadTimer(Integer timeout){
        if(timer != null){
            timer.stop();
        }
        timer = new Timer(timeout,e->{
            snakeMoveTimer();
        });
        timer.start();
    }
    private void snakeMoveTimer(){
        if (currentDirection == SnakeMoveDirection.DOWN) {
            snakeMoveDown();
        }
        if(currentDirection == SnakeMoveDirection.UP){
            snakeMoveUp();
        }
        if(currentDirection == SnakeMoveDirection.LEFT){
            snakeMoveLeft();
        }
        if (currentDirection == SnakeMoveDirection.RIGHT) {
            snakeMoveRight();
        }
        atomicBoolean.set(true);
        initSnake();
    }
    private  void snakeMoveUp(){
        // 1.获取蛇头的位置
        SnakeBody snakeHead = sankeBodyList.get(0);
        int headX = snakeHead.getBodyX();
        int headY = snakeHead.getBodyY();
        //向上移动一格
        int nextY = headY - 1;
        if(nextY < 0){
            gameOverDeal(GameOverInfo.HIT_WALL);
            return;
        }
        if (backPanels[headX][nextY].getComponentCount()>0 &&
                backPanels[headX][nextY].getComponent(0) instanceof SnakeBody) {
            gameOverDeal(GameOverInfo.HIT_SELF);
            System.out.println("向上走，撞到自己");
            return;
        }
        ifEteFood(headX,nextY);
        //更新蛇的位置
        updateSnakeBodyList(headX,nextY);
    }
    private   void snakeMoveDown(){
        // 1.获取蛇头的位置
        SnakeBody snakeHead = sankeBodyList.get(0);
        int headX = snakeHead.getBodyX();
        int headY = snakeHead.getBodyY();
        //向下移动一格
        int nextY = headY + 1;
        if(nextY > ROWS - 1){
            gameOverDeal(GameOverInfo.HIT_WALL);
            return;
        }
        if(backPanels[headX][nextY].getComponentCount()>0 &&
                backPanels[headX][nextY].getComponent(0) instanceof SnakeBody){
            gameOverDeal(GameOverInfo.HIT_SELF);
            System.out.println("向下走，撞到自己");
            return;
        }
        ifEteFood(headX,nextY);
        //更新蛇的位置
        updateSnakeBodyList(headX,nextY);
    }

    private  void snakeMoveLeft(){
        // 1.获取蛇头的位置
        SnakeBody snakeHead = sankeBodyList.get(0);
        int headX = snakeHead.getBodyX();
        int headY = snakeHead.getBodyY();
        //向左移动一格
        int nextX = headX - 1;
        if(nextX < 0){
            gameOverDeal(GameOverInfo.HIT_WALL);
            return;
        }
        if(backPanels[nextX][headY].getComponentCount()>0 &&
                backPanels[nextX][headY].getComponent(0) instanceof SnakeBody){
            gameOverDeal(GameOverInfo.HIT_SELF);
            System.out.println("向左走，撞到自己");
            return;
        }
        ifEteFood(nextX,headY);
        //更新蛇的位置
        updateSnakeBodyList(nextX,headY);
    }

    private void snakeMoveRight(){
        // 1.获取蛇头的位置
        SnakeBody snakeHead = sankeBodyList.get(0);
        int headX = snakeHead.getBodyX();
        int headY = snakeHead.getBodyY();
        //向右移动一格
        int nextX = headX + 1;
        if(nextX > COLS - 1){
            gameOverDeal(GameOverInfo.HIT_WALL);
            return;
        }
        //如果是身体
        if(backPanels[nextX][headY].getComponentCount()>0 &&
                backPanels[nextX][headY].getComponent(0) instanceof SnakeBody){
            gameOverDeal(GameOverInfo.HIT_SELF);
            System.out.println("向右走，撞到自己");
            return;
        }
        ifEteFood(nextX,headY);
        //更新蛇的位置
        updateSnakeBodyList(nextX,headY);
    }

    private void updateSnakeBodyList(int x,int y){
        for (SnakeBody snakeBody : sankeBodyList) {
            int bodyY = snakeBody.getBodyY();
            int bodyX = snakeBody.getBodyX();
            JPanel jPanel = backPanels[bodyX][bodyY];
            jPanel.removeAll();
            jPanel.revalidate();
            jPanel.repaint();
        }
        for(int i = sankeBodyList.size()-1; i>=1 ; i--){
            SnakeBody snakeBody = sankeBodyList.get(i);
            snakeBody.setBodyX(sankeBodyList.get(i-1).getBodyX());
            snakeBody.setBodyY(sankeBodyList.get(i-1).getBodyY());
        }

        //更新蛇头
        SnakeBody snakeBody = new SnakeBody(x,y,true);
        sankeBodyList.set(0,snakeBody);
    }


    private void addSnakeBody(){
        //获取蛇尾
        SnakeBody snakeBodyFirst = sankeBodyList.get(sankeBodyList.size()-1);
        SnakeBody snakeBodySecond = sankeBodyList.get(sankeBodyList.size()-2);
        int snakeBodyFirstX = snakeBodyFirst.getBodyX();
        int snakeBodyFirstBodyY = snakeBodyFirst.getBodyY();

        int snakeBodySecondX = snakeBodySecond.getBodyX();
        int snakeBodySecondBodyY = snakeBodySecond.getBodyY();

        if(snakeBodyFirstX == snakeBodySecondX){
            //蛇身在同一列
            if(snakeBodyFirstBodyY > snakeBodySecondBodyY){
                //蛇身向下
                SnakeBody snakeBody = new SnakeBody(snakeBodyFirstX,snakeBodyFirstBodyY+1);
                sankeBodyList.add(snakeBody);
            }else{
                //蛇身向上
                SnakeBody snakeBody = new SnakeBody(snakeBodyFirstX,snakeBodyFirstBodyY-1);
                sankeBodyList.add(snakeBody);
            }
        }

        if(snakeBodyFirstBodyY == snakeBodySecondBodyY){
            //蛇身在同一行
            if(snakeBodyFirstX > snakeBodySecondX){
                //蛇身向右
                SnakeBody snakeBody = new SnakeBody(snakeBodyFirstX+1,snakeBodyFirstBodyY);
                sankeBodyList.add(snakeBody);
            }else{
                //蛇身向左
                SnakeBody snakeBody = new SnakeBody(snakeBodyFirstX-1,snakeBodyFirstBodyY);
                sankeBodyList.add(snakeBody);
            }
        }

    }

    private void initSnake(){
        for(SnakeBody snakeBody : sankeBodyList){
            JPanel jPanel = backPanels[snakeBody.getBodyX()][snakeBody.getBodyY()];
            jPanel.add(snakeBody,BorderLayout.CENTER);
            jPanel.revalidate();
            jPanel.repaint();
        }
    }

    private void randomProduceFood(){
        //随机生成食物
        int foodX = (int)(Math.random()*ROWS);
        int foodY = (int)(Math.random()*COLS);

        //判断食物是否在蛇身上
        for(SnakeBody snakeBody : sankeBodyList){
            if(snakeBody.getBodyX() == foodX && snakeBody.getBodyY() == foodY){
                randomProduceFood();
                return;
            }
        }
        //生成食物
        JPanel jPanel = backPanels[foodX][foodY];
        jPanel.add(food.loadBounds(foodX,foodY),BorderLayout.CENTER);
        jPanel.revalidate();
        jPanel.repaint();
    }

    private void ifEteFood(int x,int y){
        if(backPanels[x][y].getComponentCount()>0 &&
                backPanels[x][y].getComponent(0) instanceof Food){
            //吃到食物
            //1.蛇身增加一节
            this.currentScore += 10;
            this.score.setText("当前得分："+currentScore);
            //难度增加
            if(currentScore % 50 == 0){
                Integer delay = LEVEL_TIMEOUT_MAP.get(level) - ((currentScore / 50)*25);
                if (delay <= 0){
                    delay = 25;
                }
                loadTimer(delay);
            }
            addSnakeBody();
            //2.随机生成食物
            randomProduceFood();
        }
    }

    private void gameOverDeal(GameOverInfo info){
        timer.stop();
        HistoryScoreList.addScore(currentScore);
        //游戏结束处理
         SwingUtilities.invokeLater(()->{
             UIManager.put("OptionPane.yesButtonText","重新开始?");
             int gameOver = JOptionPane.showConfirmDialog(this,
                     "游戏结束：" + info.getInfo() + "，得分：" + currentScore,
                     "GameOver",
                     JOptionPane.YES_NO_OPTION);
                if(gameOver == JOptionPane.YES_OPTION){
                    this.dispose();
                    SnakeGame snakeGame1 = new SnakeGame();
                    snakeGame1.setLevel(this.level);
                }else if (
                        gameOver == JOptionPane.NO_OPTION ||
                        gameOver == JOptionPane.CANCEL_OPTION ||
                        gameOver == JOptionPane.CLOSED_OPTION){
                  this.dispose();
                }
         });
    }



     class SnakeBodyKeyListener implements KeyListener {
        @Override
        public void keyTyped(KeyEvent e) {
        }

        @Override
        public void keyPressed(KeyEvent e) {
            synchronized (currentDirection){
                if(!atomicBoolean.get()){
                    return;
                }
                if (e.getKeyCode() == KeyEvent.VK_UP || e.getKeyCode() == KeyEvent.VK_W) {
                    if(Math.abs(currentDirection.getVar()) != SnakeMoveDirection.VERTICAL.getVar()){
                        currentDirection = SnakeMoveDirection.UP;
                    }
                }
                if(e.getKeyCode() == KeyEvent.VK_DOWN || e.getKeyCode() == KeyEvent.VK_S){
                    if(Math.abs(currentDirection.getVar()) != SnakeMoveDirection.VERTICAL.getVar()){
                        currentDirection = SnakeMoveDirection.DOWN;
                    }
                }
                if(e.getKeyCode() == KeyEvent.VK_LEFT || e.getKeyCode() == KeyEvent.VK_A){
                    if(Math.abs(currentDirection.getVar()) != SnakeMoveDirection.HORIZONTAL.getVar()){
                        currentDirection = SnakeMoveDirection.LEFT;
                    }
                }
                if (e.getKeyCode() == KeyEvent.VK_RIGHT || e.getKeyCode() == KeyEvent.VK_D) {
                    if(Math.abs(currentDirection.getVar()) != SnakeMoveDirection.HORIZONTAL.getVar()){
                        currentDirection = SnakeMoveDirection.RIGHT;
                    }
                }
                if(e.getKeyCode() == KeyEvent.VK_P){
                    addSnakeBody();
                }
                atomicBoolean.set(false);
            }
            initSnake();
        }
        @Override
        public void keyReleased(KeyEvent e) {
        }
    }

}
